"use strict";
var _evt = null;
var _pslHost = psl.get(window.location.hostname);
var _host = (_pslHost === null ? window.location.host : _pslHost);
var _data = [];
var _src_set = new Set();

function getJsonData(force) {
	return new Promise(async (resolve,reject) => {
		if (force) {
			_data = [];
			_src_set = new Set();
			var rj = await getRootJson();
			for (var jsonObj of rj.data) {
				if (!jsonObj) {
					continue;
				}
				// host可以为*,代表所有站点;
				if (jsonObj.host=="*" || jsonObj.host==_host) {
					// 修改符合条件的元素的可见属性;
					_data.push(jsonObj);

					var match = jsonObj.elem.match(/\[src='(.*?[^\\])'\]/i);
					if (match && match.length>1) {
						_src_set.add(match[1]);
					}
				}
			}
		}
		resolve(_data);
	});
}

function getPathStr(elem, useFullPath) {
	var parent = elem;
	var pathStr = elem.tagName;
	if (!useFullPath) {
		return pathStr;
	}
	while ((parent.parentNode || parent.host) && parent!=document.body) {
		parent = parent.parentNode || parent.host;
		var attrStr = "";
		if (parent.id) {
			attrStr = "#" + parent.id.trim();
		} else if (parent.className) {
			attrStr = "." + parent.className.trim().replace(/\s+/g,".");
		}
		if (parent.tagName) {
			pathStr = parent.tagName + attrStr + " > " + pathStr;
		}
	}
	return pathStr;
}

function getContainsStr(elem) {
	if (elem.tagName == "IMG") {
		return "";
	}
	return ":contains('" + elem.textContent.trim().replace(/\n/g,"\\n").replace(/'/g,"\\'") + "')";
}

function getAttrStr(elem, useFullAttr) {
	var attrStr = "";
	if ($(elem).text().trim() != "" && !useFullAttr) {
		return attrStr;
	}
	attrStr = getAttrStrByRegExp(elem,(elem.tagName=="IMG" && !useFullAttr) ? "^src$" : "^[\\w\\-]+$");
	if (attrStr == "") {
		attrStr = getAttrStrByRegExp(elem,"^[\\w\\-]+$");
	}
	return attrStr;
}

function getAttrStrByRegExp(elem, strPattern) {
	var attrStr = "";
	var objRegExp = new RegExp(strPattern,"i");
	var attrs = elem.attributes;
	for (var i=0; i<attrs.length; i++) {
		if (objRegExp.test(attrs[i].name)) {
			var attrValue = attrs[i].value.replace(/\n/g,"\\n").replace(/'/g,"\\'");
			attrStr += "[" + attrs[i].name + (attrValue == "" ? "" : "='" + attrValue + "'") + "]";
		}
	}
	return attrStr;
}

function hideItem(data, elem) {
	if (data.host=="*" || _host==data.host) {
		var items = $(data.elem);
		if (elem) {
			items = items.add(elem);
		}
		console.log("hide-item: " + items.length + " " + data.elem);
		items.each(function(){setHiddenStyle($(this),data);});
	}
}

function setHiddenStyle(elem, data) {
	if (hasOption(data,"disabled")) {
		return;
	}
	if (data != null) {
		elem.data("recyclebin-guid",data.guid);
		elem.data("data",data);
	}
	elem.data("nostyle",typeof elem.attr("style")=="undefined");
	if (hasOption(elem.data("data"),"display:none")) {
		elem.css("display","none");
	}
	elem.css("visibility","hidden");
}

function clearHiddenStyle(elem, forever) {
	if (hasOption(elem.data("data"),"prevent_delete")) {
		return;
	}
	elem.css("visibility","");
	if (hasOption(elem.data("data"),"display:none")) {
		elem.css("display","");
	}
	if (elem.data("nostyle") && elem.attr("style")=="") {
		elem.removeAttr("style");
	}
	if (forever) {
		elem.removeData("recyclebin-guid");
		elem.removeData("data")
		elem.removeData("nostyle");
	}
}

function allElems(elem) {
	var elems = $(elem).find("*").addBack();
	elems.each(function(index, element) {
		var root = element.shadowRoot;
		if (root) {
			elems = elems.add(allElems(root));
		}
	});
	return elems;
}

function throwIntoTheRecycleBin(elem, x, y, isGlobal, doReport) {
	while (elem.shadowRoot) {
		var targetElement = elem.shadowRoot.elementFromPoint(x, y);
		if (targetElement != elem) {
			elem = targetElement;
		} else {
			break;
		}
	}
	// 不处理视频元素;
	var ve = $(elem.tagName=="VIDEO"?elem:"").add($(elem).parents("VIDEO")).add($(elem).find("VIDEO"));
	if (ve.length > 0) {
		if (ve.width() > document.documentElement.clientWidth/3 && ve.height() > document.documentElement.clientHeight/3) {
			console.log("忽略大尺寸视频.");
			return;
		}
	}
	while (elem.style && elem.style.visibility=="hidden") {
		if (elem.parentNode) {
			elem = elem.parentNode;
		} else if (elem.host) {
			elem = elem.host;
		} else {
			break;
		}
	}
	if (elem.tagName == "HTML") {
		console.log("不能处理网页的顶层HTML元素.");
		return;
	}
	// 将该元素所包含的文本和所有属性转换为JQuery查询语句;
	// data为该元素的JQuery查询语句;
	var data = getPathStr(elem, false) + getContainsStr(elem) + getAttrStr(elem, false);
	var selector = getPathStr(elem, true) + getContainsStr(elem) + getAttrStr(elem, true);
	// 生成不良元素的信息对象;
	var jsonObj = {
		elem: data,
		selector: selector,
		html: elem.outerHTML,
		time: new Date().toString(),
		guid: newGuid(),
		host: (isGlobal ? "*" : _host),
		title: document.title,
		frame_url: window.location.href
	};
	// 将不良信息推入回收站;
	chrome.runtime.sendMessage({name: "push-item", data: jsonObj}, async function(result) {
		console.dir(result.data);
		if (doReport && result != "回收站: 网址在白名单中!") {
			var items = $(result.data.elem).add(elem);
			allElems("body").each(function() {
				if ($(this).data("recyclebin-guid")) {
					clearHiddenStyle($(this),false);
				}
			});
			items.css("outline","solid 3px rgba(255,0,0,0.5)").css("outlineOffset","-3px");
			var p1 = new Promise((resolve, reject) => {
				window.setTimeout(function() {
					chrome.runtime.sendMessage({name: "capture-page", data: result.data}, async function() {
						var p2 = new Promise((resolve, reject) => {
							window.setTimeout(function() {
								allElems("body").each(function() {
									if ($(this).data("recyclebin-guid")) {
										setHiddenStyle($(this),null);
									}
								});
								items.css("outline","").css("outlineOffset","");
								hideItem(result.data,elem);
								resolve();
							},1000);
						});
						await Promise.all([p2]);
						resolve();
					});
				},1000);
			});
			await Promise.all([p1]);
		} else {
			hideItem(result.data,elem);
		}
	});
}

function main() {
	chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
		//console.log("onMessage: " + message.name);

		switch (message.name) {
		case "hide-item":
			hideItem(message.data,null);
			break;
		case "restore-item":
			if (message.data.host=="*" || message.data.host==_host) {
				allElems("body").each(function() {
					if ($(this).data("recyclebin-guid") === message.data.guid) {
						clearHiddenStyle($(this),true);
					}
				});
			}
			break;
		case "init-page":
			console.log(message.data);
			break;
		case "update-data":
			getJsonData(true);
			break;
		case "clear-data":
			allElems("body").each(function() {
				if ($(this).data("recyclebin-guid")) {
					clearHiddenStyle($(this),true);
				}
			});
			break;
		case "throw-into-the-recyclebin":
			throwIntoTheRecycleBin(_evt.srcElement,_evt.clientX,_evt.clientY,false,false);
			break;
		case "throw-into-the-global-recyclebin":
			throwIntoTheRecycleBin(_evt.srcElement,_evt.clientX,_evt.clientY,true,false);
			break;
		case "throw-into-the-recyclebin-and-report":
			throwIntoTheRecycleBin(_evt.srcElement,_evt.clientX,_evt.clientY,false,true);
			break;
		case "pop-item":
		case "clear-all-data":
			chrome.runtime.sendMessage({name: message.name});
			break;
		}
	});

	// 监听指定元素及所有后代元素的鼠标右键点击事件;
	window.addEventListener("contextmenu", function(evt) {
		_evt = evt;
		// 定位shadowRoot中的元素;
		// 如果同时按了ctrl按键则进行处理;
		if (evt.ctrlKey) {
			// evt.srcElement为鼠标点击的元素;
			throwIntoTheRecycleBin(evt.srcElement,evt.clientX,evt.clientY,evt.altKey,evt.shiftKey);
			evt.stopPropagation();
			evt.preventDefault();
			return false;
		} else {
			return true;
		}
	}, true);
}

$(document).ready(function() {

	// 修改body的所有元素的可见属性;
	async function updateAllContent() {
		var p1 = new Promise(async (resolve,reject) => {					
			console.time("updateAllContent");
			var arrdata = await getJsonData(true);
			for (var jsonObj of arrdata) {
				if (!jsonObj) {
					continue;
				}
				var p2 = new Promise((resolve,reject) => {
					// 修改符合条件的元素的可见属性;
					$(jsonObj.elem).each(function(){setHiddenStyle($(this),jsonObj);});
					resolve();
				});
				await Promise.all([p2]);
			}

			console.timeEnd("updateAllContent");
			resolve();
		});
		return await Promise.all([p1]);
	}

	// 修改符合条件的元素的可见属性;
	async function updateContent(elem, onlyone) {
		var p1 = new Promise((resolve,reject) => {
			window.setTimeout(async () => {
				if (!elem) {
					return;
				}
				if (typeof(elem) != "object") {
					return;
				}
				var elems;
				if (onlyone) {
					elems = $(elem);
				} else {
					elems = $(elem).find("*").addBack();
				}
				var arrdata = await getJsonData(false);
				var len = 0;
				for (var jsonObj of arrdata) {
					if (!jsonObj) {
						continue;
					}
					var p2 = new Promise((resolve,reject) => {
						window.setTimeout(() => {
							len += elems.filter(jsonObj.elem).each(function(){setHiddenStyle($(this),jsonObj);}).length;
							resolve();
						},0);
					});
					await Promise.all([p2]);
				}
				if (onlyone && len==0 && elems.data("recyclebin-guid")) {
					clearHiddenStyle(elems,true);
				}
				resolve();
			},0);
		});
		return await Promise.all([p1]);
	}

	// 修改body及其中的所有元素;
	updateAllContent();

	// 监测DOM的变化;
	var mo = new MutationObserver(function(mutationList, observer) {
		for (var mutation of mutationList) {
			switch (mutation.type) {
			case "childList":
				for (var node of mutation.addedNodes) {
					switch (node.nodeName.toLowerCase())
					{
					case "#comment":
					case "#text":
					case "path":
					case "g":
					case "br":
					case "script":
						break;  // 忽略这些浪费时间且无用的元素;
					default:
						updateContent(node,false);
						break;
					}
				}
				break;
			case "characterData":
				updateContent(mutation.target.parentNode || mutation.target.host,true);
				break;
			case "attributes":
				if (mutation.attributeName == "src") {
					var elem = $(mutation.target);
					if (_src_set.has(mutation.target.getAttribute("src"))) {
						updateContent(mutation.target,true);
					} else if (elem.data("recyclebin-guid")) {
						updateContent(mutation.target,true);
					}
				}
				break;
			}
		}
	});

	const config = {
		subtree: true, 
		childList: true, 
		characterData: true, 
		attributes: true, 
		atributeFilter: ["src"]
	};
	mo.observe(document.body, config);
});

main();
